package org.springframework.context.event;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.util.ClassUtil;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

public abstract class AbstractApplicationEventMulticaster implements ApplicationEventMulticaster, BeanFactoryAware {

    private final Set<ApplicationListener<ApplicationEvent>> applicationListeners = new HashSet<>();

    private BeanFactory beanFactory;

    @Override
    public void addApplicationListener(ApplicationListener listener) {
        applicationListeners.add(listener);
    }

    @Override
    public void removeApplicationListener(ApplicationListener listener) {
        applicationListeners.remove(listener);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    protected List<ApplicationListener> getApplicationListeners(ApplicationEvent event) {
        List<ApplicationListener> listeners = new ArrayList<>();
        for (ApplicationListener<ApplicationEvent> listener : applicationListeners) {
            if (support(listener, event)) {
                listeners.add(listener);
            }
        }

        return listeners;
    }

    private boolean support(ApplicationListener listener, ApplicationEvent event) {
        Class<? extends ApplicationListener> listenerClass = listener.getClass();
        Class<?> targetClass = ClassUtil.isCglibProxyClass(listenerClass) ? listenerClass.getSuperclass() : listenerClass;


        ParameterizedType type = (ParameterizedType) targetClass.getGenericInterfaces()[0];
        String typeName = type.getActualTypeArguments()[0].getTypeName();

        Class eventListenerClass = null;

        try {
            eventListenerClass = Class.forName(typeName);
        } catch (Exception e) {
            throw new BeansException("wrong event class name: " + typeName);
        }
        return eventListenerClass.isAssignableFrom(event.getClass());
    }

}
